/*
 *  Update.java        22-ott-2009
 *   
 * Copyright (c) 2009 mentalsmash.org
 *  
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 *  The use of the Apache License does not indicate that this project is
 *  affiliated with the Apache Software Foundation.
 */

package org.mentalsmash.tazio.transactions.management;

import java.io.Serializable;

import org.mentalsmash.documentation.Concurrency;
import org.mentalsmash.tazio.IllegalArgumentException;
import org.mentalsmash.tazio.commons.identifiers.OID;
import org.mentalsmash.tazio.transactions.TransactionalSystemRuntimeException;
import org.mentalsmash.tazio.transactions.objects.NoSuchPropertyException;
import org.mentalsmash.tazio.transactions.objects.RootSet;
import org.mentalsmash.tazio.transactions.objects.TransactionalObject;
import org.mentalsmash.tazio.transactions.util.Utility;

/**
 * A <tt>PropertyUpdate</tt> represents a {@link TransactionalOperation} that
 * updates a property in a TransactionalObject.
 * <p>
 * Since a property update may involve only primitive types or other
 * Transactional Objects, a Property update supports only writes of {@link OID}s
 * or primitive types
 * </p>
 * 
 * @author Andrea Reale
 * @version 0.0.1
 * @synch PropertyUpdate is not synchronized. User of this container class must
 *        provide synchronization on access to those instances.
 */

@Concurrency(concurrentAccess = false, specification = "PropertyUpdate is not synchronized. User of this container class must"
	+ " provide synchronization on access to those instances."

)
public class PropertyUpdate extends TransactionalOperation {

    private static final long serialVersionUID = -7985499731087521480L;
    private Serializable writtenValue = null; // either an OID or a primitive

    // type

    // there's no need to send this over the network
    // private transient Serializable originalValue = null; // the value of the
    // property

    // before the

    // write operation

    /**
     * Create a new <tt>Update</tt> operation that acts on a given
     * <code>targetObject</code> having version <code>originalVersion</code>
     * 
     * @param targetObject
     *            the {@link OID} of the object target of this update operation
     * @param originalVersion
     *            the last committed version of the object on which the
     *            operation is taken
     */
    public PropertyUpdate(final OID targetObject, final long originalVersion) {
	super(targetObject, originalVersion,true);
    }

    /**
     * The value written on the property named as {@link #getPropertyName()} on
     * the <tt>TransactionalObject</tt> affected by this <tt>WriteOperation</tt>
     * (it is either an OID or a primitive type)
     * 
     * @return The value written on the property named as
     *         {@link #getPropertyName()} on the <tt>TransactionalObject</tt>
     *         affected by this <tt>WriteOperation</tt>
     */
    public Serializable getWrittenValue() {
	return this.writtenValue;
    }

    /**
     * Sets the value written by this update operation.
     * <p>
     * It must be either a primitive type or an OID
     * </p>
     * 
     * @param writtenValue
     *            the value that this update wrote on a property
     */
    public void setWrittenValue(final Serializable writtenValue) {
	if (!Utility.isPrimitiveType(writtenValue)
		&& !(writtenValue instanceof OID)) {
	    throw new IllegalArgumentException(
		    "Only primitive types or oid are to be written");
	}
	this.writtenValue = writtenValue;
    }

    @Override
    public int hashCode() {
	final int prime = 31;
	int result = super.hashCode();
	result = prime
		* result
		+ ((this.writtenValue == null) ? 0 : this.writtenValue
			.hashCode());
	return result;
    }

    @Override
    public boolean equals(final Object obj) {
	if (this == obj) {
	    return true;
	}
	if (!super.equals(obj)) {
	    return false;
	}
	if (getClass() != obj.getClass()) {
	    return false;
	}
	final PropertyUpdate other = (PropertyUpdate) obj;
	if (this.writtenValue == null) {
	    if (other.writtenValue != null) {
		return false;
	    }
	} else if (!this.writtenValue.equals(other.writtenValue)) {
	    return false;
	}
	return true;
    }

    @Override
    public String toString() {
	final StringBuilder builder = new StringBuilder();
	builder.append("PropertyUpdate [writtenValue=");
	builder.append(this.writtenValue);
	builder.append(", propertyName()=");
	builder.append(getPropertyName());
	builder.append(", targetObject()=");
	builder.append(getTargetObject());
	builder.append("]");
	return builder.toString();
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mentalsmash.tazio.transactions.TransactionalOperation#commitOperation
     * (org.mentalsmash.tazio.transactions.RootSet)
     */
    @Override
    public void commitOperation(final RootSet rs) {
	final TransactionalObject thisObj = rs
		.getObject(this.getTargetObject());
	if (thisObj == null) {
	    throw new TransactionalSystemRuntimeException("Object "
		    + this.getTargetObject() + " not found in root set");
	}

	try {
	    Serializable propertyToWrite;

	    if (getWrittenValue() == null) {
		log.error("getWrittenValue() was null");
		throw new TransactionalSystemRuntimeException(
			"Cannot write a null value");
	    }
	    if (getWrittenValue() instanceof OID) { // getWrittenValue is a
		// reference to another
		// transactional object
		final OID oid = (OID) getWrittenValue();

		if (oid.equals(OID.NULL_OID)) { // it's just a write to null
		    propertyToWrite = null;
		} else {
		    propertyToWrite = rs.getObject(oid);
		    if (propertyToWrite == null) {
			log.error("Unable to retrieve object {} from root set"
				+ oid);
			throw new TransactionalSystemRuntimeException(
				"CUnable to retrieve object from root set");
		    }
		}
	    } else { // getWrittenValue is a primitive type
		propertyToWrite = getWrittenValue();
	    }

	    thisObj.lockObject(); // acquiring exclusive lock for applying
	    // changes
	    try {
		thisObj.setPropertyValue(getPropertyName(), propertyToWrite);
	    } finally {
		thisObj.unlockObject();
	    }
	} catch (final NoSuchPropertyException e) {
	    log.error("Expected property {} not found.", getPropertyName());
	    throw new TransactionalSystemRuntimeException("Expected property "
		    + getPropertyName() + " was not found");
	}

    }
}
